﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.ComponentModel;
using GameEditor.Common;

namespace GameEditor.Common.Controls
{

    public enum CurveEditMode
    {
        Null,
        FixedValue,
        Noise,
        Linear,
        Spline,
        Oscillate
    }

    public class CurveEditor : UserControl
    {
        private List<ControlPoint> controlPoints = new List<ControlPoint>();
        public ControlPoint[] ControlPoints
        {
            get
            {
                return controlPoints.ToArray();
            }
            set
            {
                controlPoints.Clear();
                if (value == null)
                    return;
                controlPoints.AddRange(
                    from cp in value orderby cp.Time select cp);
                UpdateValueBounds();
                Invalidate();
            }
        }

        private OscillateType oscillateType = OscillateType.Sine;
        private float frequency;
        private float @base;
        private float amplitude;
        private float phase;
        private CurveEditMode editMode = CurveEditMode.Linear;


        public OscillateType OscillateType
        {
            get
            {
                return oscillateType;
            }
            set
            {
                oscillateType = value;
                Invalidate();
            }
        }
        /// <summary>
        /// 波频率
        /// </summary>
        public float Frequency 
        {
            get
            {
                return frequency;
            }
            set
            {
                frequency = value;
                Invalidate();
            }
        }
        /// <summary>
        /// 波基准偏移
        /// </summary>
        public float Base
        {
            get
            {
                return @base;
            }
            set
            {
                @base = value;
                UpdateValueBounds();
                Invalidate();
            }
        }
        /// <summary>
        /// 波振幅
        /// </summary>
        public float Amplitude
        {
            get
            {
                return amplitude;
            }
            set
            {
                amplitude = value;
                UpdateValueBounds();
                Invalidate();
            }
        }
        /// <summary>
        /// 相位
        /// </summary>
        public float Phase 
        {
            get
            {
                return phase;
            }
            set
            {
                phase = value;
                Invalidate();
            }
        }
        public CurveEditMode EditMode
        {
            get
            {
                return editMode;
            }
            set
            {
                editMode = value;
                UpdateValueBounds();
                Invalidate();
            }
        }
        

        public CurveEditor()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            OscillateType = OscillateType.Sine;
            Amplitude = 100;
            Base = 0;
            Frequency = 1;
            EditMode = CurveEditMode.Oscillate;
            Font = new Font(Font.FontFamily, 9);
            rightAlignFormat = new StringFormat();
            rightAlignFormat.Alignment = StringAlignment.Far;
            rightAlignFormat.LineAlignment = StringAlignment.Center;
        }
        protected override void Dispose(bool disposing)
        {
            rightAlignFormat.Dispose();
            base.Dispose(disposing);
        }

        private const int ValuePanelWidth = 40;
        private const int TimePanelHeight = 30;
        //Value Panel Padding
        private const int TopPadding = 10;
        private const int RightPadding = 5;
        private const int LeftPadding = 5;
        private const int BottomPadding = 10;
        private static readonly Size CPHandleSize = new Size(6, 6);
        private int selectedCP = -1;


        private int ResolutionY = 100;
        private const int TimeUnitWidth = 50;
        //private const float TimeUnit = 0.1f;

        private float lowerBound = 0;
        private float upperBound = 600;
        private int textHeight = 10;
        private int timeTextWidth = 30;
        private float timeResolution = 1;
        private float initialTime = 0;//-0.3f;


        private StringFormat rightAlignFormat;

        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.Clear(Color.Black);

            DrawSidePanels(g);

            g.SetClip(new Rectangle(ValuePanelWidth, 0, Width - ValuePanelWidth, Height ));
            DrawGrid(g);
            g.SetClip(new Rectangle(ValuePanelWidth, 0, Width - ValuePanelWidth, Height - TimePanelHeight));



            switch (editMode)
            {
                case CurveEditMode.Oscillate:
                    DrawOscillateWave(g);
                    break;
                case CurveEditMode.Linear:
                case CurveEditMode.Spline:
                    SmoothingMode mode = g.SmoothingMode;
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    DrawCurve(g);

                    g.SmoothingMode = mode;
                    DrawCPHandle(g);
                    DrawCPInfo(g);
                    break;
                case CurveEditMode.FixedValue:
                    DrawFixedValue(g);
                    break;
                case CurveEditMode.Noise:
                    DrawNoise(g);
                    break;
            }
        }


        private void DrawCPInfo(Graphics g)
        {
            if (selectedCP == -1)
                return;
            Rectangle r = GetCPHandle(selectedCP);
            ControlPoint cp = controlPoints[selectedCP];
            string info = string.Format("{0:n}s, {1:n}", cp.Time, cp.Value);
            r.X = r.Right + 10;
            SizeF textSize = g.MeasureString(info, Font);

            r.Width = (int)textSize.Width + 6;
            r.Height = (int)textSize.Height + 4;

            g.FillRectangle(Brushes.Gray, r);
            g.DrawRectangle(Pens.BurlyWood, r);
            g.DrawString(info, Font, Brushes.Yellow, r);


            
        }

        private void DrawGrid(Graphics g)
        {
            Pen pen = Pens.Green;
            int refTop = TopPadding;
            int refBottom = Height - TimePanelHeight - BottomPadding;
            int formWidth = Width;
            int height = refBottom - refTop;
            int rows = (int)(upperBound - lowerBound) / ResolutionY; 

            //绘制水平线
            for (int i = 0; i < rows+1; i++)
            {
                int y = height * i / rows + refTop;
                g.DrawLine(pen, new Point(ValuePanelWidth, y), new Point(formWidth, y));
            }
            //绘制垂直线
            //获取一个最接近的起始点
            float startTime =(int) (initialTime);// (int)(initialTime * 10) * 0.1f;
            //if (startTime < initialTime)
            //    startTime += timeResolution;

            float x = ValuePanelWidth + (startTime - initialTime) * TimeUnitWidth;
            float gridBottom = Height - TimePanelHeight;
            Font font = Font;
            Brush brush = Brushes.Yellow;
            Rectangle rect = new Rectangle();
            rect.Y = Height - TimePanelHeight;
            rect.Width = timeTextWidth;
            rect.Height = textHeight;

            for (; x < formWidth; x += TimeUnitWidth * timeResolution, startTime+=timeResolution)
            {
                startTime = 1.0f * (x - ValuePanelWidth) / TimeUnitWidth;
                
                string s = string.Format("{0}s", (int)(Math.Round(startTime + initialTime)));// + 0.5f));
                rect.X = (int)(x);// - timeTextWidth / 2);
                
                g.DrawLine(pen, new PointF(x, 0), new PointF(x, gridBottom));
                g.DrawString(s, font, brush, rect);
            }
        }

        private Rectangle GetCPHandle(int i)
        {
            int refTop = TopPadding;
            int refBottom = Height - TimePanelHeight - BottomPadding;
            int height = refBottom - refTop;
            float boundDelta = upperBound - lowerBound;
            Rectangle rect = new Rectangle();
            ControlPoint cp = controlPoints[i];
            rect.X = (int)((cp.Time - initialTime) * TimeUnitWidth) + ValuePanelWidth;
            float rel = (1 - (cp.Value - lowerBound) / boundDelta);
            rect.Y = (int)(rel * height);

            rect.X -= CPHandleSize.Width / 2;
            rect.Y -= CPHandleSize.Height / 2;
            rect.Width = CPHandleSize.Width;
            rect.Height = CPHandleSize.Height;
            return rect;
            

        }

        private void DrawCPHandle(Graphics g)
        {
            int bottom = Height - TimePanelHeight;
            int width = Width;
            Brush brush = Brushes.Brown;
            Pen pen = Pens.Pink;
            for (int i = 0; i < controlPoints.Count; i++)
            {
                Rectangle r = GetCPHandle(i);
                if (r.X < ValuePanelWidth || r.Y <0)
                    continue;
                if (r.Right > width || r.Bottom > bottom)
                    continue;
                g.FillRectangle(brush, r);
                g.DrawRectangle(pen, r);

            }

        }
        private void DrawOscillateWave(Graphics g)
        {
            float freq = Frequency;
            float phase = Phase;
            float amp = Amplitude;
            float bas = Base;

            if (OscillateType == OscillateType.Square)
            {
                DrawFunc(g,
                    x => ((float)Math.Sign(Math.Sin(x * freq + phase)) * amp + bas)
                        , 1, (TimeUnitWidth), false);


            }
            else
            {
                //绘制正弦波
                DrawFunc(g,
                    x => ((float)Math.Sin(x * freq + phase) * amp + bas)
                        , 5, (TimeUnitWidth),true);

            }
        }

        private void DrawFixedValue(Graphics g)
        {
            int refTop = TopPadding;
            int refBottom = Height - TimePanelHeight - BottomPadding;
            int formWidth = Width;
            int height = refBottom - refTop;
            int rows = (int)(upperBound - lowerBound) / ResolutionY;
            float boundDelta = upperBound - lowerBound;
            int y = (int)((1 - (Base - lowerBound) / boundDelta) * height) + refTop;

            g.DrawLine(Pens.Lime, new Point(ValuePanelWidth, y), new Point(Width, y));

        }

        private void DrawNoise(Graphics g)
        {

            Random r = new Random();


            int max = (int)(Base + Amplitude);
            int min = (int)(Base - Amplitude);

            int delta = max - min;

            DrawFunc(g,
                x => (float)r.Next() % delta + min
                    , 5, (TimeUnitWidth), false);
        }

        private void DrawFunc(Graphics g, Func<float, float> f, int step, float pixelPerSeconds, bool curve)
        {
            int refTop = TopPadding;
            int refBottom = Height - TimePanelHeight - BottomPadding;
            int formWidth = Width;
            int height = refBottom - refTop;
            int rows = (int)(upperBound - lowerBound) / ResolutionY;
            float boundDelta = upperBound - lowerBound;

            List<Point> points = new List<Point>(); 
            for (int x = 0; x < formWidth - ValuePanelWidth; x += step)
            {
                float t = (float)(1.0f * x / pixelPerSeconds) + initialTime;
                float v = f(t);// ((float)Math.Sin(t * freq + phase) * amp + bas);

                int y = (int)((1 - (v - lowerBound) / boundDelta) * height) + refTop;


                points.Add(new Point(x + ValuePanelWidth, y));
            }
            if (curve)
                g.DrawCurve(Pens.Lime, points.ToArray());
            else
                g.DrawLines(Pens.Lime, points.ToArray());
        }

        private void DrawCurve(Graphics g)
        { 
            int refTop = TopPadding;
            int refBottom = Height - TimePanelHeight - BottomPadding;
            int formWidth = Width;
            int height = refBottom - refTop;
            int rows = (int)(upperBound - lowerBound) / ResolutionY;

            float boundDelta = upperBound - lowerBound ;


            if (controlPoints == null || controlPoints.Count == 0)
                return;


            Point[] points = new Point[controlPoints.Count];
            for (int i = 0; i < controlPoints.Count; i++)
            {
                ControlPoint cp = controlPoints[i];
                points[i].X = (int)((cp.Time - initialTime) * TimeUnitWidth) + ValuePanelWidth;
                points[i].Y = (int)((1 - (cp.Value - lowerBound) / boundDelta) * height);

            }

            switch (EditMode)
            {
                case CurveEditMode.Linear:
                    g.DrawLines(Pens.Lime, points);
                    break;
                case CurveEditMode.Spline:
                    g.DrawCurve(Pens.Lime, points);
                    break;
            }
        }

        private void DrawSidePanels(Graphics g)
        {
            Pen pen = Pens.Yellow;
            Font font = Font;
            Brush brush = Brushes.Yellow;
            int y = Height - TimePanelHeight;
            Point[] lines =
            {
                new Point(ValuePanelWidth,0),
                new Point(ValuePanelWidth,y),
                new Point(Width,y)
            };
            g.DrawLines(pen, lines);

            int refTop = TopPadding;
            int refBottom = y - BottomPadding;


            Rectangle rect = new Rectangle(LeftPadding, refTop - textHeight / 2, ValuePanelWidth - RightPadding - LeftPadding, textHeight);
            g.DrawString(upperBound.ToString(), font, brush, rect, rightAlignFormat);

            float mid = (lowerBound + upperBound) / 2;


            rect = new Rectangle(LeftPadding, (refTop+refBottom)/2 - textHeight / 2, ValuePanelWidth - RightPadding - LeftPadding, textHeight);
            g.DrawString(mid.ToString(), font, brush, rect, rightAlignFormat);


            rect = new Rectangle(LeftPadding, refBottom - textHeight / 2, ValuePanelWidth - RightPadding - LeftPadding, textHeight);
            g.DrawString(lowerBound.ToString(), font, brush, rect, rightAlignFormat);

        }


        private int TimeToPixel(float time)
        {
            return (int)(TimeUnitWidth * time);
        }

        private float PixelToTime(int pixel)
        {
            return 1.0f * pixel / TimeUnitWidth;
        }


        public int CPHitTest(Point p)
        {
            for (int i = 0; i < controlPoints.Count; i++)
            {
                Rectangle r = GetCPHandle(i);
                if (r.Contains(p))
                    return i;
            }
            return -1;
        }

        private Point lastPosition=Point.Empty;

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            selectedCP = -1;
            lastPosition = e.Location;
            if (e.Button == MouseButtons.Left)
            {
                selectedCP = CPHitTest(e.Location);
                UpdateCursor();
                Invalidate();
            }
        }


        private void UpdateCursor()
        {

            if (selectedCP == -1)
                Cursor = Cursors.Arrow;
            else
                Cursor = Cursors.SizeAll;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);


            UpdateCursor();


            if (e.Button == MouseButtons.Left && selectedCP != -1 && controlPoints.Count != -1)
            {
                int dx = e.X - lastPosition.X;
                int dy = e.Y - lastPosition.Y;
                float dt = 0;
                float dv = 0;

                int refTop = TopPadding;
                int refBottom = Height - TimePanelHeight - BottomPadding;
                int height = refBottom - refTop;

                float boundDelta = upperBound - lowerBound ;

                dt = PixelToTime(dx);
                dv =  - dy * boundDelta / height;


                ControlPoint cp = controlPoints[selectedCP];
                //新的时间不可以比前面的控制点小，也不可以比后面的大
                float newTime = cp.Time + dt;
                if (selectedCP > 0)
                {
                    newTime = Math.Max(controlPoints[selectedCP - 1].Time, newTime);
                }
                if (selectedCP < controlPoints.Count - 1)
                {
                    newTime = Math.Min(controlPoints[selectedCP + 1].Time, newTime);
                }

                controlPoints[selectedCP].Time = newTime;
                controlPoints[selectedCP].Value += dv;

                UpdateValueBounds();
                Invalidate();
            }

            if (e.Button == MouseButtons.Middle)
            {
                int dx = e.X - lastPosition.X;
                float dt = PixelToTime(dx);
                initialTime -= dt;
                Cursor = Cursors.SizeAll;
                Invalidate();

            }

            lastPosition = e.Location;
        }

        private void UpdateValueBounds()
        {
            switch (editMode)
            {
                case CurveEditMode.Noise:
                case CurveEditMode.Oscillate:
                    lowerBound = Base - Amplitude - 20;
                    upperBound = Base + Amplitude + 20;
                    break;
                case CurveEditMode.Linear:
                case CurveEditMode.Spline:
                    if (controlPoints.Count == 0)
                        return;
                    float newLower = controlPoints[0].Value;
                    float newUpper = newLower;
                    foreach (ControlPoint cp in controlPoints)
                    {
                        newLower = Math.Min(cp.Value, newLower);
                        newUpper = Math.Max(cp.Value, newUpper);

                    }
                    lowerBound = newLower - 20;
                    upperBound = newUpper + 40;
                    break;
                case CurveEditMode.FixedValue:
                    lowerBound=Base-20;
                    upperBound=Base+20;
                    break;
            } 
            ResolutionY = (int)(upperBound - lowerBound) / 10;
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            selectedCP = -1;
            UpdateCursor();
            Invalidate();
        }

    }
}
